package lists;

import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;

public class MyLinkedList<T> implements MyList<T>, MyQueue<T>, MyStack<T> {

	private static class Node<T> {
		T element;
		Node<T> next;
		Node<T> previous;

		public Node(T element, Node<T> next, Node<T> prev) {
			this.element = element;
			this.next = next;
			this.previous = prev;
		}
	};

	private Node<T> header;

	private int size;

	public MyLinkedList() {
		header = new Node<T>(null, header, header);

		size = 0;
	}

	public MyLinkedList(MyList<T> list) {
		this();

		Iterator<? extends T> it = list.iterator();
		while (it.hasNext()) {
			add(it.next());
		}

	}

	private Node<T> find(int index) throws MyIndexOutOfBoundsException {
		if (index > size || index < 0) {
			throw new MyIndexOutOfBoundsException("Incorrect index");
		}
		Node<T> e = header;
		if (index < size / 2) {
			for (int i = -1; i < index; i++) {
				e = e.next;
			}

		} else {
			for (int i = size; i > index; i--) {
				e = e.previous;
			}
		}
		return e;
	}

	@Override
	public void add(T e) {
		Node<T> node;
		if (size == 0) {
			node = new Node<T>(e, header, header);
		} else {
			node = new Node<T>(e, header, header.previous);
		}
		node.next.previous = node;
		node.previous.next = node;

		size++;
	}

	public void addFirst(T e) {
		add(0, e);
	}

	public void addLast(T e) {

		Node<T> node;
		if (size == 0) {
			node = new Node<T>(e, header, header);
		} else {
			node = new Node<T>(e, header, header.previous);
		}
		node.next.previous = node;
		node.previous.next = node;

		size++;

	}

	@Override
	public void add(int index, T e) {
		Node<T> header = null;
		try {
			header = find(index);
		} catch (MyIndexOutOfBoundsException e1) {
			System.out.println(e1.getMessage());
		}

		Node<T> node;
		if (size == 0) {
			node = new Node<T>(e, header, header);
		} else {
			node = new Node<T>(e, header, header.previous);
		}
		node.next.previous = node;
		node.previous.next = node;

		size++;

	}

	@Override
	public void addAll(Collection<? extends T> e) {
		Iterator<? extends T> it = e.iterator();
		while (it.hasNext()) {
			add(it.next());
		}

	}

	@Override
	public void addAll(int index, Collection<? extends T> e) {
		Iterator<? extends T> it = e.iterator();
		while (it.hasNext()) {
			add(index++, it.next());
		}

	}

	public T getFirst() {
		return get(0);
	}

	public T getLast() {
		return get(size - 1);
	}

	@Override
	public T get(int index) {
		Node<T> node = null;
		if (index == size) {
			try {
				throw new MyIndexOutOfBoundsException("Incorrent index!");
			} catch (MyIndexOutOfBoundsException e) {

				System.out.println(e.getMessage());
				throw new NullPointerException();
			}
		}
		try {
			node = find(index);
		} catch (MyIndexOutOfBoundsException e) {

			System.out.println(e.getMessage());
		}

		return node.element;
	}

	public T removeFirst() {
		return remove(0);
	}

	public T removeLast() {
		return remove(size - 1);
	}

	@Override
	public T remove(int index) {
		Node<T> node = null;
		if (index == size) {
			try {
				throw new MyIndexOutOfBoundsException("Incorrent index!");
			} catch (MyIndexOutOfBoundsException e) {

				System.out.println(e.getMessage());
				throw new NullPointerException();
			}
		}

		try {
			node = find(index);
		} catch (MyIndexOutOfBoundsException e) {

			System.out.println(e.getMessage());
		}
		T element = node.element;
		node.previous.next = node.next;
		node.next.previous = node.previous;

		node.element = null;
		node.next = null;
		node.previous = null;
		size--;

		return element;
	}

	@Override
	public void clear() {
		if (header.next != null) {
			Node<T> e = header.next;
			while (e != header) {
				Node<T> node = e.next;
				e.next = e.previous = null;
				e.element = null;
				e = node;
			}
			header.next = header.previous = header;
			size = 0;
		}
	}

	@Override
	public boolean isEmpty() {

		return size == 0;
	}

	@Override
	public void set(int index, T e) {
		Node<T> node = null;
		if (index == size) {
			try {
				throw new MyIndexOutOfBoundsException("Incorrent index!");
			} catch (MyIndexOutOfBoundsException e2) {

				System.out.println(e2.getMessage());
				throw new NullPointerException();
			}
		}
		try {
			node = find(index);
		} catch (MyIndexOutOfBoundsException e1) {
			System.out.println(e1.getMessage());
		}
		node.element = e;

	}

	@Override
	public int indexOf(Object e) {

		if (e == null) {
			int i = 0;
			while ((i < size - 1) && (get(i) != e)) {
				i++;
			}
			if (get(i) == e) {
				return i;
			} else {
				return -1;
			}

		} else {
			int i = 0;
			while ((i < size - 1) && (!e.equals(get(i)))) {
				i++;
			}
			if (e.equals(get(i))) {
				return i;
			} else {
				return -1;
			}

		}

	}

	@Override
	public int size() {

		return size;
	}

	@Override
	public Object[] toArray() {
		if (size != 0) {
			Object[] array = new Object[size];
			for (int i = 0; i < array.length; i++) {
				array[i] = get(i);
			}
			return array;
		} else {
			return null;
		}
	}

	@Override
	public Iterator<T> iterator() {

		return new Iterator<T>() {
			private int count;

			@Override
			public boolean hasNext() {

				return count < size;
			}

			@Override
			public T next() {
				if (hasNext()) {
					T element = get(count);
					count++;
					return element;
				} else {
					throw new NoSuchElementException();
				}
			}

			@Override
			public void remove() {
				throw new UnsupportedOperationException();

			}
		};
	}

	public Iterator<T> descendingIterator() {
		return new Iterator<T>() {
			private int count = size - 1;

			@Override
			public boolean hasNext() {

				return count >= 0;
			}

			@Override
			public T next() {
				if (hasNext()) {
					T element = get(count);
					count--;
					return element;
				} else {
					throw new NoSuchElementException();
				}
			}

			@Override
			public void remove() {
				throw new UnsupportedOperationException();

			}
		};

	}

	@Override
	public void push(T e) {
		addLast(e);

	}

	@Override
	public T pop() {

		return getLast();
	}

	@Override
	public void offer(T e) {
		addLast(e);

	}

	@Override
	public T peek() {

		return getFirst();
	}

	@Override
	public T poll() {

		return removeFirst();
	}

}